/** <!--------------------------------------------------------------------------------------------->
 * @file RevGraphBranch.cpp
 *
 * GSvn - A Graphical Client for SVN
 * Copyright (C) 2012  Karl Krach, gsvn@bluespirit.la
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 <!---------------------------------------------------------------------------------------------> */

// -- includes --
#include "RevGraphBranch.h"
#include "RevGraphItem.h"
#include "RevGraphBranchLink.h"
#include <QMap>
#include <QPainter>
#include <QDebug>
#include <QEvent>
#include <limits>

/** <!--------------------------------------------------------------------------------------------->
 * Creates the RevGraphBranch object
 <!---------------------------------------------------------------------------------------------> */
RevGraphBranch::RevGraphBranch( const QString& strName ) : QGraphicsItem(), m_strName( strName ),
    m_pBranchLink(NULL)
{
	setPos( 0, 0 );
	setFlag( QGraphicsItem::ItemSendsScenePositionChanges, true );

}	// END RevGraphBranch()

/** <!--------------------------------------------------------------------------------------------->
 * Deletes the RevGraphBranch object
 <!---------------------------------------------------------------------------------------------> */
RevGraphBranch::~RevGraphBranch()
{
	if( m_pBranchLink != NULL )
	{
		delete m_pBranchLink;
		m_pBranchLink = NULL;
	}
}	// END ~RevGraphBranch()

/** <!--------------------------------------------------------------------------------------------->
 * Sets the branch link (iterator object of the branch-map which connects all branches)
 <!---------------------------------------------------------------------------------------------> */
void RevGraphBranch::setBranchLink( const RevGraphBranchLink& pBranchLink )
{
	m_pBranchLink = new RevGraphBranchLink(pBranchLink);

	QList<RevGraphBranch *> pPreviousBranches = pBranchLink.getAllPrevious();
	setX( pPreviousBranches.count() * RevGraphItem::getStaticBoundingWidth() );

	foreach( RevGraphBranch* pBranch, pBranchLink.getAllNext() )
	{
		if( pBranch != this )
		{
			pBranch->setX( pBranch->x() + RevGraphItem::getStaticBoundingWidth() );
		}
	}
}	// END setBranchLink( QMap<BranchIndex, RevGraphBranch*>::iterator )

/** <!--------------------------------------------------------------------------------------------->
 * Returns true, when the given item was already added to this branch
 <!---------------------------------------------------------------------------------------------> */
bool RevGraphBranch::containsItem( RevGraphItem* pItem ) const
{
	return m_aItems.contains(pItem->getRevisionID());

}	// END containsItem( RevGraphItem* )

/** <!--------------------------------------------------------------------------------------------->
 * Adds an item to the branch
 *
 * @param pItem (in) item to add
 <!---------------------------------------------------------------------------------------------> */
void RevGraphBranch::addItem( RevGraphItem* pItem )
{
	if( !containsItem(pItem) )
	{
		prepareGeometryChange();
		RevGraphItemLink pBranchLink = m_aItems.insert( pItem );
		pItem->installSceneEventFilter( this );
		pItem->setBranchLink( pBranchLink );
		pItem->setX( x() );
	}
	else qWarning() << "Item with ID " << pItem->getRevisionID() << "is already registered to branch "
	                << m_strName;

}	// END addItem( RevGraphItem* )

/** <!--------------------------------------------------------------------------------------------->
 * Removes the given item from the branch
 *
 * @param pItem (in) item to remove
 <!---------------------------------------------------------------------------------------------> */
void RevGraphBranch::removeItem( RevGraphItem* pItem )
{
	if( containsItem(pItem) )
	{
		prepareGeometryChange();
		pItem->removeSceneEventFilter( this );
		m_aItems.remove( pItem );
	}
	else
	{
		qWarning() << "Cannot remove item with revID" << pItem->getRevisionID();
	}

}	// END removeItem( RevGraphItem* )

/** <!--------------------------------------------------------------------------------------------->
 * Removes all items from this branch
 <!---------------------------------------------------------------------------------------------> */
void RevGraphBranch::removeAllItems()
{
	prepareGeometryChange();
	m_aItems.removeAll();

}	// END removeAllItems()

/** <!--------------------------------------------------------------------------------------------->
 * Calculates the y-position of this branch (browses through all branches and returns the y-pos of
 * the first item
 *
 * @returns the y-position of this branch
 <!---------------------------------------------------------------------------------------------> */
qreal RevGraphBranch::calculateYPosition() const
{
	bool bYPosDetermined = false;
	qreal dYPos = std::numeric_limits<qreal>::max();

	//
	// Go through all branches (incl. the own
	//
	foreach( const RevGraphBranch* pBranch, m_pBranchLink->getAll() )
	{
		if( pBranch->hasItem() )
		{
			const RevGraphItem* pFirstItem = pBranch->getFirstItem();
			qreal dItemY = mapFromItem(pFirstItem, pFirstItem->getItemBoundingRect()).boundingRect().y();
			if( dItemY < dYPos )
			{
				bYPosDetermined = true;
				dYPos= dItemY;
			}
		}
	}

	return bYPosDetermined ? dYPos : 0;

}	// END calculateYPosition() const

/** <!--------------------------------------------------------------------------------------------->
 * Calculates the bounding rectangular of all child elements
 *
 * @returns the bounding rectangular of all child elements
 <!---------------------------------------------------------------------------------------------> */
QRectF RevGraphBranch::calculateChildBoundingRect() const
{
	QRectF oBoundingRect;
	foreach( const RevGraphItem* pItem, m_aItems.getAll() )
	{
		QRectF oItemBoundingRect = mapFromItem(pItem, pItem->getItemBoundingRect()).boundingRect();
		oBoundingRect = oBoundingRect.united( oItemBoundingRect );
	}
	return oBoundingRect;

}	// END calculateChildBoundingRect() const

/** <!--------------------------------------------------------------------------------------------->
 * This function defines the outer bounds of the item as a rectangle; all painting must be
 * restricted to inside an item's bounding rect. QGraphicsView uses this to determine whether
 * the item requires redrawing.
 *
 * Note: For shapes that paint an outline / stroke, it is important to include half the pen
 * width in the bounding rect. It is not necessary to compensate for antialiasing, though.
 *
 * @returns the item's bounding rect
 <!---------------------------------------------------------------------------------------------> */
QRectF RevGraphBranch::boundingRect() const
{
	// Get bounding rect of all children
	QRectF oBoundingRect = calculateChildBoundingRect();
	// Get Y position from all other branches
	oBoundingRect.setY( calculateYPosition() - S_HEIGHT-S_SPACE );

	oBoundingRect.setHeight( S_HEIGHT );

	return oBoundingRect.adjusted( -S_SPACE, -S_SPACE, +S_SPACE, +S_SPACE);

}	// END boundingRect() const

/** <!--------------------------------------------------------------------------------------------->
 * This function, which is usually called by QGraphicsView, paints the contents of an item in
 * local coordinates.
 *
 * @param pPainter (in) painter to paint the graphics item
 * @param pOption  (in) provides the style for the item (such as its state, exposed area, etc)
 * @param pWidget  (in) points to the widget that is being painted on
 <!---------------------------------------------------------------------------------------------> */
void RevGraphBranch::paint( QPainter* pPainter, const QStyleOptionGraphicsItem* pOption,
                            QWidget* pWidget )
{
	Q_UNUSED( pOption );
	Q_UNUSED( pWidget );

	pPainter->save();

	QRectF oBranchRect = boundingRect().adjusted( +S_SPACE, +S_SPACE, -S_SPACE, -S_SPACE);

	pPainter->setPen( Qt::NoPen );
	pPainter->setBrush( QBrush(Qt::gray, Qt::FDiagPattern) );
	pPainter->drawRect( oBranchRect );

	pPainter->setPen( QColor(Qt::black) );
	pPainter->drawText( oBranchRect.adjusted( +S_SPACE, +S_SPACE, -S_SPACE, -S_SPACE),
	                    m_strName, QTextOption(Qt::AlignCenter) );

	pPainter->restore();

}	// END paint( QPainter*, const QStyleOptionGraphicsItem*, QWidget* )

/** <!--------------------------------------------------------------------------------------------->
 * Receives all events of the watched graphics item.
 *
 * Reimplementations must return true to prevent further processing of a given event, ensuring
 * that it will not be delivered to the watched item, or return false to indicate that the event
 * should be propagated further by the event system.
 *
 * @param pWatched (in) watched graphics item
 * @param pEvent   (in) filtered event
 * @returns true, when this event shall be blocked
 <!---------------------------------------------------------------------------------------------> */
bool RevGraphBranch::sceneEventFilter( QGraphicsItem* pWatched, QEvent* pEvent )
{
	Q_UNUSED( pWatched );
	Q_UNUSED( pEvent );

//	switch( pEvent->type() )
//	{
//		case QEvent::GraphicsSceneMove:
//		qDebug() << m_strName << "GraphicsSceneMove of " << (void*) pWatched;
//		break;
//	case QEvent::GraphicsSceneResize:
//		qDebug() << m_strName << "GraphicsSceneResize of " << (void*) pWatched;
//		break;
//	case QEvent::Move:
//		qDebug() << m_strName << "Move of " << (void*) pWatched;
//		break;
//	case QEvent::Resize:
//		qDebug() << m_strName << "Resize of " << (void*) pWatched;
//		break;
//	default:
//		qDebug() << m_strName << "sceneEventFilter" << pEvent->type();
//		break;
//	}
//	fflush( stdout );
//	fflush( stderr );


	return false;

}	// END sceneEventFilter( QGraphicsItem*, QEvent* )

/** <!--------------------------------------------------------------------------------------------->
 * This virtual function is called by QGraphicsItem to notify custom items that some part of the
 * item's state changes. By reimplementing this function, your can react to a change, and in some
 * cases, (depending on change,) adjustments can be made.
 *
 * @param eChange (in) is the parameter of the item that is changing
 * @param oValue  (in) is the new value; the type of the @a oValue depends on @a eChange
 <!---------------------------------------------------------------------------------------------> */
QVariant RevGraphBranch::itemChange( GraphicsItemChange eChange, const QVariant& oValue )
{
	if( eChange == ItemPositionChange )
	{
		QPointF oNewPos = oValue.toPointF();

		if( !m_aItems.isEmpty() )
		{
			qreal dXpos = m_aItems.getFirst()->x();
			if( qAbs(dXpos - oNewPos.x()) < 0.0001 )
			{
				prepareGeometryChange();
				//
				// Update children when branch position has changed
				//
				foreach( RevGraphItem* pItem, m_aItems.getAll() )
				{
					pItem->setX( oNewPos.x() );
				}
			}
		}
	}
	return QGraphicsItem::itemChange(eChange, oValue);

}	// END itemChange( GraphicsItemChange, const QVariant& )













